#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <queue>
#include <vector>
#include <set>
#include "sqlite3ext.h"
#include "SQLiteCpp/Database.h"
#include "SQLiteCpp/Transaction.h"
#include "SQLiteCpp/Statement.h"
#include "SQLiteCpp/VariadicBind.h"
#include "boost/filesystem.hpp"
#include "hashlib/i_hashlib.h"
#include "filter.h"
#include "log.h"
using namespace boost::filesystem;
using namespace SQLite;
const char* FILTER_FILE_TABLE[]
{
	".cpp",
	".cxx",
	".cc",
	".c",
	".pl",
	".h",
	".hpp",
	".go",
	".pch",
	".pdb",
	".rsp",
	".html",
	".css",
	".js",
	".gch",
	".d",
	".i",
	".ii",
	".yaml",
	".yml",
	".md",
	".py",
	".json",
	".sh"
};


std::string getTimeStamp(long long sec)
{
	auto mTime = std::chrono::seconds(sec);
	auto tp = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>(mTime);
	auto tt = std::chrono::system_clock::to_time_t(tp);
	struct tm * timeinfo;
	char buffer[64];
	timeinfo = localtime(&tt);

	strftime(buffer, 64, "%F %T", timeinfo);
	return buffer;
}
bool filterFile(const std::string& ext)
{
	for (int i = 0; i < sizeof(FILTER_FILE_TABLE) / sizeof(char*); i++)
	{
		if (ext == FILTER_FILE_TABLE[i])
		{
			return true;
		}
	}
	return false;
}

struct Record
{
	Record(std::string ihashStr,
		int64_t isize,
		std::string filename,
		long long iatime,
		long long imtime,
		std::string ipath
	)
		:hashStr(ihashStr)
		, size(isize)
		,filename(filename)
		,atime(iatime)
		,mtime(imtime)
		,path(ipath)
	{

	}

	Record() = default;
	std::string hashStr;
	int64_t size;
	std::string filename;
	long long atime;
	long long mtime;
	std::string path;
};

class Recorder
{
public:
	Recorder()
		:db("G:\\ShowLib\\Video\\.showlib\\Storage.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE)
		, bexit(false)
	{
		db.exec("create table if not exists showlib("
			"hash varchar(256),"
			"size INTEGER,"
			"name TEXT,"
			"atime INTEGER,"
			"mtime INTEGER,"
			"path TEXT PRIMARY KEY"
			")");
	};
	~Recorder() = default;
public:
	void Insert(const Record& record)
	{
		std::lock_guard<std::mutex> lock(mtx);
		q.push(record);
	}
	void Insert(Record&& record)
	{
		std::lock_guard<std::mutex> lock(mtx);
		q.push(record);
	}
	void Save()
	{
		std::queue<Record> p;
		while (!bexit || !q.empty())
		{
			std::unique_lock<std::mutex> lck(mtxCV);
			produce.wait_for(lck, std::chrono::seconds(1));
			
			{
				std::lock_guard<std::mutex> lock(mtx);
				std::swap(p, q);
			}
			std::queue<Record> b(p);
			bool isSuccess = false;
			try
			{
				SQLite::Transaction transaction(db);
				while (!p.empty())
				{
					SQLite::Statement   query(db, "INSERT INTO showlib VALUES(?,?,?,?,?,?)");
					SQLite::bind(query, p.front().hashStr, p.front().size, p.front().filename, p.front().atime, p.front().mtime, p.front().path);
					query.exec();
					p.pop();
				}
				transaction.commit();
				isSuccess = true;
			}
			catch (const std::exception& e)
			{
				std::cout << e.what() << std::endl;
			}

			if (!isSuccess)
			{
				while (!b.empty())
				{
					try
					{
						SQLite::Statement   query(db, "INSERT INTO showlib VALUES(?,?,?,?,?,?)");
						SQLite::bind(query, b.front().hashStr, b.front().size, b.front().filename, b.front().atime, b.front().mtime, b.front().path);
						query.exec();
						b.pop();
					}
					catch (const std::exception& e)
					{
						std::cout << e.what() << std::endl;
						BOOST_LOG_SEV(vdsu_logger::get(), info) << e.what() << " " << __LINE__;
					}
				}
			}
		}

	};
	void exit()
	{
		bexit = true;
	}
private:
	std::mutex mtxCV;
	std::mutex mtx;
	std::condition_variable produce;
	std::queue<Record> q;
	SQLite::Database db;
	bool bexit;
};
void walkdir(path p, HashLibPtr& sha256, Database& db)
{
	if (!is_directory(p))
	{
		return;
	}

	directory_iterator iterbegin(p);
	directory_iterator iterend;
	for (auto iter = iterbegin; iter != iterend; iter++)
	{
		std::cout << iter->path().string() << std::endl;
		std::cout << iter->path().filename().string() << std::endl;
		if (iter->status().type() == directory_file)
		{
			auto x = iter->path().filename().string();
			if (iter->path().string() == R"(E:/Users/admin/Downloads\boost_1_74_0\boost_1_74_0)")
			{
				continue;
			}
			if (x == ".git" || x == ".showlib" || x == "$RECYCLE.BIN")
			{
				continue;
			}
			walkdir(iter->path(), sha256, db);
		}
		else
		{
			auto & subPath = iter->path();
			if (filterFile(subPath.filename().extension().string()))
			{
				std::cout << "filter  " << subPath.filename().string() << std::endl;
				continue;
			}
			fstream f(iter->path().string(),std::ios::binary|std::ios::in);
			auto hashstr = sha256->hexdigest(f, "sha256");
			std::cout << hashstr << std::endl;
			std::cout << subPath.filename().string() << std::endl;
			//std::cout <<  << std::endl;

			try
			{
				SQLite::Statement   query(db, "INSERT INTO showlib VALUES(?,?,?,?,?,?)");
				struct _stat64 buf;
				_stat64(iter->path().string().c_str(), &buf);
				SQLite::bind(query, hashstr, buf.st_size, subPath.filename().string(), buf.st_atime, buf.st_mtime, subPath.string());
				query.exec();
			}
			catch (const std::exception& e)
			{
				std::cerr << e.what() << std::endl;
				BOOST_LOG_SEV(vdsu_logger::get(), info) << e.what() << " " << __LINE__;
			}
		}
		
	}
}

void walkdir(path p, HashLibPtr& sha256, Recorder& r)
{
	if (!is_directory(p))
	{
		return;
	}

	directory_iterator iterbegin(p);
	directory_iterator iterend;
	for (auto iter = iterbegin; iter != iterend; iter++)
	{
		std::cout << iter->path().string() << std::endl;
		std::cout << iter->path().filename().string() << std::endl;
		if (iter->status().type() == directory_file)
		{
			auto x = iter->path().filename().string();
			if (x == ".git" || x == ".showlib" || x == "$RECYCLE.BIN")
			{
				continue;
			}
			walkdir(iter->path(), sha256, r);
		}
		else
		{
			auto & subPath = iter->path();
			//if (filterFile(subPath.filename().extension().string()))
			//{
			//	std::cout << "filter  " << subPath.filename().string() << std::endl;
			//	continue;
			//}
			auto ext = subPath.filename().extension().string();
			for_each(ext.begin(), ext.end(), tolower);
			if (!isVideo(ext))
			{
				continue;
			}
			fstream f(iter->path().string(), std::ios::binary | std::ios::in);
			auto hashstr = sha256->hexdigest(f, "sha256");
			if (hashstr.empty())
			{
				BOOST_LOG_SEV(vdsu_logger::get(), info) << subPath.string() << " " << __LINE__;
			}
			std::cout << hashstr << std::endl;
			std::cout << subPath.filename().string() << std::endl;
			//std::cout <<  << std::endl;

			try
			{
				struct _stat64 buf;
				_stat64(iter->path().string().c_str(), &buf);
				r.Insert(Record(hashstr, buf.st_size, subPath.filename().string(), buf.st_atime, buf.st_mtime, subPath.string()));
			}
			catch (const std::exception& e)
			{
				BOOST_LOG_SEV(vdsu_logger::get(), info) << subPath.string() << " " << e.what() << "" << __LINE__;
				std::cerr << e.what() << std::endl;
			}
		}

	}
}
//300 000 000 ms
//362 212
//381 269 7
//279 783 
//492 679

int getCount(SQLite::Database& db)
{
	SQLite::Statement query(db, std::string{ "select count (*) from showlib"});
	query.executeStep();
	int count = query.getColumn(0);
	std::cerr << count << std::endl;
	return count;
}

int getPathSet(SQLite::Database& db, std::set<std::string>& setPath)
{
	try
	{
		SQLite::Statement query(db, std::string{ "SELECT path FROM showlib" });
		while (query.executeStep())
		{
			std::string x = query.getColumn(0);
			setPath.insert(x);
		}
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return setPath.size();
};

int getHashSet(SQLite::Database& db, std::set<std::string>& setHash)
{
	try
	{
		SQLite::Statement query(db, std::string{ "SELECT hash FROM showlib" });
		while (query.executeStep())
		{
			std::string x = query.getColumn(0);
			setHash.insert(x);
		}
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return 0;
};

int getHash(SQLite::Database& db, std::string hash)
{
	try
	{
		std::string sql = "SELECT * FROM showlib where hash='";
		sql += hash;
		sql += "'";
		SQLite::Statement query(db, sql);
		
		std::queue<Record> p;
		while (query.executeStep())
		{
			Record r = query.getColumns<Record, 6>();
			p.push(r);

		}
		if (p.size() < 2)
		{
			return 0;
		}
		Record r = p.front();
		BOOST_LOG_SEV(vdsu_logger::get(), info) << "==================================================" << __LINE__;
		BOOST_LOG_SEV(vdsu_logger::get(), info) << r.hashStr << __LINE__;
		while(!p.empty())
		{
			Record r = p.front();
			//BOOST_LOG_SEV(vdsu_logger::get(), info) << r.hashStr << __LINE__;
			BOOST_LOG_SEV(vdsu_logger::get(), info) << r.size << __LINE__;
			BOOST_LOG_SEV(vdsu_logger::get(), info) << r.filename << __LINE__;
			BOOST_LOG_SEV(vdsu_logger::get(), info) << r.path << __LINE__;
			p.pop();
		}
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return 0;
};


int move(SQLite::Database& db, SQLite::Database& db2)
{
	try
	{
		SQLite::Statement query(db, std::string{ "SELECT * FROM showlib" });
		std::queue<Record> p;
		while (query.executeStep())
		{
			Record r = query.getColumns<Record, 6>();
			p.push(r);
			if (p.size() > 100)
			{
				SQLite::Transaction transaction(db2);
				while (!p.empty())
				{
					SQLite::Statement   query(db2, "INSERT INTO showlib VALUES(?,?,?,?,?,?)");
					SQLite::bind(query, p.front().hashStr, p.front().size, p.front().filename, p.front().atime, p.front().mtime, p.front().path);
					query.exec();
					p.pop();
				}
				transaction.commit();
			}
		}
		SQLite::Transaction transaction(db2);
		while (!p.empty())
		{
			SQLite::Statement   query(db2, "INSERT INTO showlib VALUES(?,?,?,?,?,?)");
			SQLite::bind(query, p.front().hashStr, p.front().size, p.front().filename, p.front().atime, p.front().mtime, p.front().path);
			query.exec();
			p.pop();
		}
		transaction.commit();
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return 0;
};

int insertHash(SQLite::Database& db, std::set<std::string>& setHash)
{

	std::queue<std::string> p;
	for (auto& item : setHash)
	{
		p.push(item);
		if (p.size() > 100)
		{
			try
			{
				SQLite::Transaction transaction(db);
				while (!p.empty())
				{
					SQLite::Statement   query(db, "INSERT INTO showlib VALUES(?)");
					SQLite::bind(query, p.front());
					query.exec();
					p.pop();
				}
				transaction.commit();
			}
			catch (const std::exception& e)
			{
				std::cout << e.what() << std::endl;
			}

		}
	}

	try
	{
		SQLite::Transaction transaction(db);
		while (!p.empty())
		{
			SQLite::Statement   query(db, "INSERT INTO showlib VALUES(?)");
			SQLite::bind(query, p.front());
			query.exec();
			p.pop();
		}
		transaction.commit();
	}
	catch (const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return 0;
}
int main()
{

	g_InitLog(debug, debug);

	BOOST_LOG_SEV(vdsu_logger::get(), info) << "Hello World!";
	setlocale(LC_ALL, "");
	path p(R"(H:\)");

	auto sha256 = getHashLib(MD_SHA256);

	//SQLite::Database db("G:\\ShowLib\\Video\\.showlib\\showlib.db", SQLite::OPEN_READWRITE| SQLite::OPEN_CREATE);
	//db.exec("create table if not exists showlib("
	//	"hash varchar(256),"
	//	"size INTEGER,"
	//	"name TEXT,"
	//	"atime INTEGER,"
	//	"mtime INTEGER,"
	//	"path TEXT"
	//")");
	SQLite::Database db2("G:\\ShowLib\\Video\\.showlib\\Storage.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
	db2.exec("create table if not exists showlib("
		"hash varchar(256),"
		"size INTEGER,"
		"name TEXT,"
		"atime INTEGER,"
		"mtime INTEGER,"
		"path TEXT PRIMARY KEY"
		")");

	SQLite::Database db3("G:\\ShowLib\\Video\\.showlib\\jy.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
	db3.exec("create table if not exists showlib("
		"hash varchar(256) PRIMARY KEY"
		")");
	std::chrono::system_clock::time_point begin_time = std::chrono::system_clock::now();
	//move(db,db2);
	//getCount(db);
	//getCount(db2);
	std::set<std::string> setHash;
	int n = getHashSet(db2, setHash);
	std::cerr << n << std::endl;

	insertHash(db3,setHash);
	getCount(db3);
	//for (auto item : setHash)
	//{
	//	getHash(db2,item);
	//}



	std::chrono::system_clock::time_point end_time = std::chrono::system_clock::now();
	std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end_time - begin_time).count() << std::endl;
}